﻿/*
* 作者:haojielyb
* 邮箱:haojielyb@qq.com
*/

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Furion;
using Furion.Logging;
using Lxb.Model;
using Lxb.Model.Service;
using MqttModel;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Server;
using Newtonsoft.Json;
using SKIT.FlurlHttpClient.Wechat.Api.Models;
using SqlSugar;
using StackExchange.Redis;

namespace FaceMqttClient
{
    public class Mqttlxb
    {
        private static MQTTnet.Client.IMqttClient mqttClient;
        private static ConcurrentDictionary<string, Mqttstate> mytopics =
            new ConcurrentDictionary<string, Mqttstate>();

        private static ConcurrentDictionary<string, MqttLinkStat> tips =
            new ConcurrentDictionary<string, MqttLinkStat>();

        public static async void ConnectMqttServerAsync()
        {
            String path = App.Configuration["facepath"]; // @"E:\weixin\FaceImage\";

            try
            {
                StringLoggingPart.Default().SetMessage($"图片保存路径 {path}").LogWarning();

                //OthTool.Base64StringToImage(b64, $@"{path}/test.jpg");
                var ds = App.GetService<DeviceService>();
                var facedowns = App.GetService<FacedownService>();

                var factory = new MqttFactory();

                mqttClient = factory.CreateMqttClient();

                var mqttserver = App.Configuration["mqttserver"].ToString();
                var options = new MqttClientOptionsBuilder()
                    .WithTcpServer(mqttserver, 61613)
                    .WithCredentials("haojie", "123123")
                    .WithClientId(Guid.NewGuid().ToString().Substring(0, 5))
                    .Build();

                mqttClient.ApplicationMessageReceivedAsync += async e =>
                {
                    var msg = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
                    //Console.WriteLine($"### {DateTime.Now.ToString()}收到信息 ###");
                    //Console.WriteLine($" Topic : {e.ApplicationMessage.Topic}");//主题
                    //Console.WriteLine($" Payload:\n {msg}");//页面信息
                    //Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");//消息等级
                    //// Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");//是否保留
                    //Console.WriteLine();

                    switch (e.ApplicationMessage.Topic)
                    {
                        case topics.heartbeat: // "mqtt/face/heartbeat":

                            var hb = JsonConvert.DeserializeObject<HeartBeat>(msg);

                            Device d = new Device();
                            d.SN = hb.info.facesluiceId;
                            d.status = 1;

                            await ds.OnlineAsync(d);

                            var id = hb.info.facesluiceId;
                            Dodingyue(id);
                            DoPublishAsync(id);
                            facedowns.delete(d.SN);
                            // 心跳
                            break;

                        case topics.basic: // "mqtt/face/basic":

                            // 上下线
                            break;
                        case topics.WechatMessage:
                            Console.WriteLine("palload" + msg);
                            try
                            {
                                var wxmsg =
                                    JsonConvert.DeserializeObject<CgibinMessageTemplateSendRequest>(
                                        msg
                                    );
                                Console.WriteLine("openid" + wxmsg.ToUserOpenId);
                            }
                            catch (Exception me)
                            {
                                Console.WriteLine("data" + me.Message);
                            }

                            break;

                        default:

                            if (e.ApplicationMessage.Topic.StartsWith("mqtt/face/"))
                            {
                                var topica = e.ApplicationMessage.Topic.Split('/');

                                if (topica.Length == 4)
                                {
                                    switch (topica[3])
                                    {
                                        case "Rec":
                                            try
                                            {
                                                //b保存打卡记录
                                                await SaveRecode(path, msg);
                                            }
                                            catch (Exception ex)
                                            {
                                                StringLoggingPart
                                                    .Default()
                                                    .SetMessage(ex.Message)
                                                    .LogWarning();
                                                File.AppendAllText(
                                                    AppDomain.CurrentDomain.BaseDirectory
                                                        + "unsave.errlog",
                                                    msg
                                                );
                                                //
                                            }
                                            break;

                                        case "Ack":

                                            var akc = JsonConvert.DeserializeObject<Ack>(msg);
                                            if (
                                                (akc.info.AddErrInfo != null)
                                                && (akc.info.AddErrInfo.Count > 0)
                                            )
                                            {
                                                string logs = "";
                                                ds.errorlog(akc.info.AddErrInfo, topica[2]);

                                                Parallel.ForEach(
                                                    akc.info.AddErrInfo,
                                                    item =>
                                                    {
                                                        logs += item.ToString();
                                                        facedowns.Upstate(
                                                            topica[2],
                                                            Convert.ToInt32(item.customId),
                                                            0,
                                                            item.errcode
                                                        );
                                                    }
                                                );
                                            }
                                            if (
                                                (akc.info.AddSucInfo != null)
                                                && (akc.info.AddSucInfo.Count > 0)
                                            )
                                            {
                                                Parallel.ForEach(
                                                    akc.info.AddSucInfo,
                                                    item =>
                                                    {
                                                        facedowns.Upstate(
                                                            topica[2],
                                                            Convert.ToInt32(item.customId)
                                                        );
                                                    }
                                                );
                                            }
                                            if (mytopics.ContainsKey(topica[2]))
                                            {
                                                if (akc.code == "410")
                                                {
                                                    if (mytopics[topica[2]] != Mqttstate.初始化)
                                                        mytopics[topica[2]] = Mqttstate.错误;
                                                }
                                                else
                                                {
                                                    if (mytopics[topica[2]] != Mqttstate.初始化)
                                                    {
                                                        mytopics[topica[2]] = Mqttstate.就绪;
                                                    }
                                                }
                                            }
                                            break;

                                        default:
                                            break;
                                    }
                                }
                                else
                                {
                                    Console.WriteLine("errors");
                                }
                            }
                            if (e.ApplicationMessage.Topic.StartsWith("mqtt/rfid/")) { }

                            break;
                    }
                };

                //重连机制
                mqttClient.DisconnectedAsync += (
                    async e =>
                    {
                        Console.WriteLine("与服务器断开连接！");

                        StringLoggingPart
                            .Default()
                            .SetMessage($"连接服务器失败 Msg：{e}")
                            .LogError();
                        tips.Clear();
                        mytopics.Clear();
                        await Task.Delay(TimeSpan.FromSeconds(5));
                        try
                        {
                            await mqttClient.ConnectAsync(options);
                            Basetopic();
                        }
                        catch (Exception exp)
                        {
                            Console.Write($"重新连接服务器失败 Msg：{exp}");
                        }
                    }
                );

                await mqttClient.ConnectAsync(options);

                Console.Write("连接服务器成功！");
                StringLoggingPart.Default().SetMessage("连接服务器成功").LogWarning();
                Basetopic();
            }
            catch (Exception exp)
            {
                StringLoggingPart.Default().SetMessage($"连接服务器失败 Msg：{exp}").LogError();
                Console.Write($"连接服务器失败 Msg：{exp}");
            }
        }

        /// <summary>
        /// 保存刷脸记录
        /// </summary>
        /// <param name="path"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        private static async Task SaveRecode(string path, string msg)
        {
            //Console.WriteLine("打卡");
            var kqs = App.GetService<Kaoqin_logService>();
            var rdsconn = App.GetService<ConnectionMultiplexer>();

            var rec = JsonConvert.DeserializeObject<Rec>(msg);
            //            logger.LogError();
            StringLoggingPart
                .Default()
                .SetMessage($"{rec.info.personId}打卡 {rec.info.facesluiceId}")
                .LogInformation();
            Kaoqin_log kl = new Kaoqin_log();
            kl.cdate = DateTime.Now;
            kl.time = DateTime.Now; //Convert.ToDateTime(rec.info.time);
            kl.datep = kl.time.ToString("yyyy/MM/dd");
            kl.timep = kl.time.ToString("H:mm:ss");
            kl.authority = "facedoor2";
            kl.name = rec.info.persionName;
            kl.Id = rec.info.customId.Trim();
            if (String.IsNullOrEmpty(kl.Id))
            {
                kl.Id = rec.info.personId;
            }
            kl.sn = rec.info.facesluiceId;
            kl.scor = Convert.ToDecimal(rec.info.similarity1);
            var redis = rdsconn.GetDatabase(0);
            if (redis.KeyExists(kl.sn))
            {
                var db = App.GetService<ISqlSugarClient>();
                var adve = db.Queryable<Device>().Where(x => x.SN == kl.sn).First();
                redis.StringSet(kl.sn, adve.address, TimeSpan.FromMinutes(10));
            }
            try
            {
                kl.addr = redis.StringGet(kl.sn);
            }
            catch (Exception)
            {
                // StringLoggingPart.Default().SetMessage($"{kl.sn}地址获取错误").LogInformation();
                Console.WriteLine("地址获取错误");
            }

            if (kl.addr == null)
            {
                //  StringLoggingPart.Default().SetMessage($"{kl.sn}地址获取为空").LogInformation();
                kl.addr = "  ";
            }
            kl.wxtz = 0;
            kl.state = 0;
            kl.io = 0;
            kl.UID = "";

            try
            {
                String dir = kl.time.ToString("yyMMdd");
                if (!Directory.Exists(path + dir))
                {
                    Directory.CreateDirectory(path + dir);
                }

                string imgPath = $"{kl.Id}_{DateTime.Now.Ticks}.jpg";

                OthTool.Base64StringToImage(rec.info.pic.Substring(23), $@"{path}{dir}/{imgPath}");

                kl.photo = $"{dir}/{imgPath}";
                Console.WriteLine("文件保存");
            }
            catch (Exception exr)
            {
                StringLoggingPart.Default().SetMessage($"文件保存失败：{exr.Message}").LogError();
            }

            await kqs.AddAsync(kl);
        }

        /// <summary>
        /// 订阅设备
        /// </summary>
        /// <param name="id"></param>
        private static void Dodingyue(string id)
        {
            if (!tips.ContainsKey(id))
            {
                try
                {
                    Subscribe(topics.getRec(id));
                    Subscribe(topics.getSnap(id));
                    Subscribe(topics.getAck(id));
                    tips[id] = new MqttLinkStat();
                    StringLoggingPart.Default().SetMessage($"{id} 上线").LogWarning();
                }
                catch (Exception e)
                {
                    StringLoggingPart
                        .Default()
                        .SetMessage($"{id} 上线 异常 {e.Message}")
                        .LogError();
                }
            }
            else
            {
                tips[id].dt = DateTime.Now;
                StringLoggingPart.Default().SetMessage($"{id} 心跳").LogInformation();
            }
        }

        /// <summary>
        /// 取消订阅
        /// </summary>
        /// <param name="id"></param>
        private static void Doundingyue(string id)
        {
            Unsubscribe(topics.getRec(id));
            Unsubscribe(topics.getSnap(id));
            Unsubscribe(topics.getAck(id));
        }

        private static void DoPublishAsync(string v)
        {
            Task.Run(() =>
            {
                if (!mytopics.ContainsKey(v))
                {
                    mytopics[v] = Mqttstate.初始化;
                }
                /// Console.WriteLine(mytopics[v] + " 状态 ");

                if (mytopics[v] == Mqttstate.初始化)
                {
                    try
                    {
                        mytopics[v] = Mqttstate.就绪;
                        var uses = App.GetService<UserService>();

                        int i = 0;
                        List<VUsers> lu = new List<VUsers>();
                        int size = 10;
                        var hasadd = true;
                        int ernum = 0;
                        do
                        {
                            if (mytopics[v] == Mqttstate.就绪)
                            {
                                ernum = 0;
                                // 获取没有下发或者修改过资料的用户
                                lu = uses.Getall(v, size, i);
                                if (lu == null)
                                {
                                    break;
                                }
                                if (lu.Count > 0)
                                {
                                    hasadd = true;

                                    PersonA_U personA_U = new PersonA_U();

                                    foreach (var item in lu)
                                    {
                                        //uses.SaveFile(item);

                                        personA_U.info.Add(
                                            new InfoItem
                                            {
                                                customId = item.user_id.ToString(),
                                                name = item.username,
                                                telnum1 = "",
                                                picURI =
                                                    $"https://weixin.tslexunbao.com/uploads/{item.face}",
                                            }
                                        );
                                    }

                                    personA_U.PersonNum = personA_U.info.Count;
                                    if (personA_U.PersonNum > 0)
                                    {
                                        personA_U.messageId = DateTime.Now.ToString();
                                        Publish(
                                            topics.get(v),
                                            JsonConvert.SerializeObject(personA_U)
                                        );

                                        i++;
                                        mytopics[v] = Mqttstate.下发;
                                    }
                                    else
                                    {
                                        break;
                                    }
                                    Thread.Sleep(5000);
                                }
                                else
                                {
                                    hasadd = false;
                                }
                            }
                            if (mytopics[v] == Mqttstate.错误)
                            {
                                // 上一页出现问题重新来
                                if (i > 0)
                                    i--;
                                mytopics[v] = Mqttstate.就绪;

                                Thread.Sleep(5000);
                            }
                            if (mytopics[v] == Mqttstate.下发)
                            {
                                ernum++;
                                //   Console.WriteLine($"轮空次数{ernum}");
                                if (ernum >= 50)
                                {
                                    mytopics[v] = Mqttstate.初始化;
                                    break;
                                }
                            }
                            //  Console.WriteLine("等1秒");
                            Thread.Sleep(1000);
                            // Console.WriteLine("1秒后");
                        } while (lu.Count == size);

                        i = 0;
                        if (!hasadd)
                        {
                            List<Users_del> del_user = new List<Users_del>();
                            do
                            {
                                if (mytopics[v] == Mqttstate.就绪)
                                {
                                    // 添加发送发完成 发送删除的用户
                                    del_user = uses.GetDel(v, size, i);

                                    if (del_user.Count > 0)
                                    {
                                        Persondelete per_del = new Persondelete();

                                        foreach (var item in del_user)
                                        {
                                            per_del.info.customId.Add(item.user_id.ToString());
                                            Console.WriteLine($"删除{item.user_id}");
                                        }

                                        per_del.PersonNum = per_del.info.customId.Count;
                                        if (per_del.PersonNum > 0)
                                        {
                                            per_del.messageId = DateTime.Now.ToString();
                                            Publish(
                                                topics.get(v),
                                                JsonConvert.SerializeObject(per_del)
                                            );

                                            i++;
                                            mytopics[v] = Mqttstate.下发;
                                        }
                                        else
                                        {
                                            break;
                                        }
                                        Thread.Sleep(5000);
                                    }
                                }
                                if (mytopics[v] == Mqttstate.错误)
                                {
                                    // 上一页出现问题重新来
                                    if (i > 0)
                                        i--;
                                    mytopics[v] = Mqttstate.就绪;

                                    Thread.Sleep(5000);
                                }
                                if (mytopics[v] == Mqttstate.下发)
                                {
                                    ernum++;
                                    Console.WriteLine($"轮空次数{ernum}");
                                    if (ernum >= 50)
                                    {
                                        mytopics[v] = Mqttstate.初始化;
                                        break;
                                    }
                                }
                            } while (del_user.Count == size);
                        }
                        mytopics[v] = Mqttstate.初始化;
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                        Console.WriteLine(ex.StackTrace);
                    }
                    finally
                    {
                        //Console.WriteLine("本轮结束");
                        mytopics[v] = Mqttstate.初始化;
                    }
                }
            });
        }

        /// <summary>
        /// remark
        /// </summary>
        /// <returns></returns>
        public static void Basetopic()
        {
            // Console.WriteLine("订阅主题");
            Subscribe(topics.heartbeat);
            Subscribe(topics.basic);
            //Subscribe(topics.facepaybeat);


            Subscribe(topics.WechatMessage);
        }

        public static void offline()
        {
            foreach (var item in tips)
            {
                if (item.Value.dt < DateTime.Now.AddMinutes(-10))
                {
                    var st = tips.TryRemove(item);
                    if (st)
                    {
                        Doundingyue(item.Key);
                    }
                    StringLoggingPart.Default().SetMessage($"{item.Key} 超时离线").LogInformation();
                }
            }
        }

        /// <summary>
        /// 订阅
        /// </summary>
        /// <param name="topicName"></param>
        private static async void Subscribe(string topicName)
        {
            string topic = topicName.Trim();
            if (string.IsNullOrEmpty(topic))
            {
                // Console.Write("订阅主题不能为空！");
                return;
            }

            //  Console.WriteLine("订阅主题" + topicName);
            if (!mqttClient.IsConnected)
            {
                Console.Write("MQTT客户端尚未连接！");
                return;
            }
            try
            {
                await mqttClient.SubscribeAsync(
                    new MqttTopicFilterBuilder().WithTopic(topic).Build()
                );
            }
            catch (Exception e)
            {
                StringLoggingPart
                    .Default()
                    .SetMessage($"订阅 {topicName} 异常" + e.Message)
                    .LogWarning();
                throw;
            }
        }

        /// <summary>
        /// 取消订阅
        /// </summary>
        /// <param name="topicName"></param>
        private static async void Unsubscribe(string topicName)
        {
            string topic = topicName.Trim();
            if (string.IsNullOrEmpty(topic))
            {
                // Console.Write("订阅主题不能为空！");
                return;
            }

            if (!mqttClient.IsConnected)
            {
                Console.Write("MQTT客户端尚未连接！");
                return;
            }
            await mqttClient.UnsubscribeAsync(topic);
        }

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="message"></param>
        private static async void Publish(string topicName, string message)
        {
            string topic = topicName.Trim();
            string msg = message.Trim();

            if (string.IsNullOrEmpty(topic))
            {
                Console.Write("主题不能为空！");
                return;
            }
            if (!mqttClient.IsConnected)
            {
                Console.Write("MQTT客户端尚未连接！");
                return;
            }

            var MessageBuilder = new MqttApplicationMessageBuilder()
                .WithTopic(topic)
                .WithPayload(msg)
                .WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.ExactlyOnce)
                .WithRetainFlag()
                .Build();

            await mqttClient.PublishAsync(MessageBuilder);
        }
    }

    public enum Mqttstate
    {
        初始化 = 0,
        就绪 = 1,
        下发 = 2,
        错误 = 3,
    }

    public class MqttLinkStat
    {
        public MqttLinkStat()
        {
            live = 10;
            dt = DateTime.Now;
        }

        public int live { get; set; }
        public DateTime dt { get; set; }
    }
}
